#ifndef __BUOLA_GL_CPROGRAM_H__
#define __BUOLA_GL_CPROGRAM_H__

#include <buola/gl.h>
#include <buola/gl/cshader.h>
#include <buola/gl/cuniform.h>

namespace buola { namespace gl {

class CProgram
{
public:
    CProgram()
        :   mID(0)
    {}

    ~CProgram()
    {
        if(mID)
            Delete();
    }

    void CreateAndAttach(const char *pVertexCode,const char *pFragmentCode)
    {
        CShader lGlyphVertex(CShader::VERTEX,pVertexCode);
        lGlyphVertex.Compile();
        CShader lGlyphFragment(CShader::FRAGMENT,pFragmentCode);
        lGlyphFragment.Compile();
        Create();
        Attach(lGlyphVertex);
        Attach(lGlyphFragment);
    }
    
    void Create()
    {
        if(mID) Delete();
        mID=glCreateProgram();
    }

    void Attach(const CShader &pShader)
    {
        glAttachShader(mID,pShader.GetID());
    }

    void Detach(const CShader &pShader)
    {
        glDetachShader(mID,pShader.GetID());
    }

    void Delete()
    {
        glDeleteProgram(mID);
        mID=0;
    }

    void Link(bool pCheck=true)
    {
        glLinkProgram(mID);

        if(pCheck)
        {
            GLint lStatus;
            glGetProgramiv(mID,GL_LINK_STATUS,&lStatus);
            if(!lStatus)
            {
                GLint lLogLen=0;

                glGetProgramiv(mID,GL_INFO_LOG_LENGTH,&lLogLen);

                char lData[lLogLen];
                GLint lRead=0;
                glGetProgramInfoLog(mID,lLogLen,&lRead,lData);

                throw XInternal(std::string("error linking program: ")+lData);
            }
        }
    }

    void BindFragmentData(GLuint pColorNumber,const char *pName)
    {
        glBindFragDataLocation(mID,pColorNumber,pName);
    }

    void BindFragmentData(GLuint pColorNumber,const std::string &pName)
    {
        glBindFragDataLocation(mID,pColorNumber,pName.c_str());
    }

    void BindAttribLocation(GLuint pAttribNumber,const char *pName)
    {
        glBindAttribLocation(mID,pAttribNumber,pName);
    }

    void BindAttribLocation(GLuint pAttribNumber,const std::string &pName)
    {
        glBindAttribLocation(mID,pAttribNumber,pName.c_str());
    }

    CUniform GetUniform(const char *pName)
    {
        GLint lLoc=glGetUniformLocation(mID,pName);

        if(lLoc<0) throw XNotFound("uniform variable "+std::string(pName)+" not found");

        return CUniform(lLoc);
    }

    CUniform GetUniform(const std::string &pName)
    {
        return GetUniform(pName.c_str());
    }

    GLint GetAttribLocation(const char *pName)
    {
        return glGetAttribLocation(mID,pName);
    }

    GLint GetAttribLocation(const std::string &pName)
    {
        return GetAttribLocation(pName.c_str());
    }

    void Use()
    {
        glUseProgram(mID);
    }

    GLuint GetID()      {   return mID; }

    static void UseNone()
    {
        glUseProgram(0);
    }
    
    explicit operator bool()
    {
        return mID!=0;
    }

private:
    GLuint mID;
};

/*namespace gl*/ } /*namespace buola*/ }

#endif
